package cc.iotkit.onenet;

import cc.iotkit.onenet.data.DeviceInfo;
import cc.iotkit.onenet.data.DeviceInfoRepository;
import cc.iotkit.onenet.data.DeviceLog;
import cc.iotkit.onenet.data.DeviceLogRepository;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootApplication
public class Application implements CommandLineRunner {

    private static final Vertx vertx = Vertx.vertx();

    @Autowired
    private DeviceInfoRepository deviceInfoRepository;
    @Autowired
    private DeviceLogRepository deviceLogRepository;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    private void startGateway() {
        HttpServer backendServer = vertx.createHttpServer();

        Router backendRouter = Router.router(vertx);

        backendRouter.route().handler(BodyHandler.create())
                .handler(rc -> {
                    try {
                        String path = rc.request().path();
                        System.out.printf("request path:%s%n", path);
                        Map<String, Object> httpHeader = getData(rc.request().headers());
                        System.out.printf("request header:%s%n", JsonUtil.toJsonString(httpHeader));
                        Map<String, List<String>> httpParams = getListData(rc.request().params());
                        System.out.printf("request params:%s%n", JsonUtil.toJsonString(httpParams));

                        String contentType = rc.request().headers().get("Content-Type");
                        if (path.endsWith("query")) {
                            Object result = new Object();
                            String name = httpParams.get("name").get(0);
                            if ("deviceInfo".equals(name)) {
                                String op = httpParams.get("op").get(0);
                                if ("find".equals(op)) {
                                    result = deviceInfoRepository.findAll();
                                } else if ("get".equals(op)) {
                                    result = deviceInfoRepository.findByProductIdAndDeviceName(
                                            httpParams.get("productId").get(0),
                                            httpParams.get("deviceName").get(0)
                                    );
                                }
                            } else if ("deviceLog".equals(name)) {
                                result = deviceLogRepository.findByProductIdAndDeviceName(httpParams.get("productId").get(0),
                                        httpParams.get("deviceName").get(0));
                            }

                            rc.response()
                                    .putHeader("content-type", "application/json")
                                    .end(JsonUtil.toJsonString(result));

                        } else if ("application/json".equals(contentType)) {
                            JsonObject body = rc.getBodyAsJson();
                            System.out.printf("request body:%s%n", body.toString());
                            JsonObject msg = new JsonObject(body.getString("msg"));
                            String productId = msg.getString("productId");
                            String deviceName = msg.getString("deviceName");
                            String messageType = msg.getString("messageType");
                            String notifyType = msg.getString("notifyType");
                            DeviceInfo deviceInfo = deviceInfoRepository.findByProductIdAndDeviceName(productId, deviceName);
                            if (deviceInfo == null) {
                                deviceInfo = new DeviceInfo();
                                deviceInfo.setId(productId+deviceName);
                                deviceInfo.setProductId(productId);
                                deviceInfo.setDeviceName(deviceName);
                            }

                            JsonObject data = msg.getJsonObject("data");
                            if (messageType.equals("lifeCycle")) {
                                deviceInfo.setStatus(data.getString("status"));
                                deviceInfoRepository.save(deviceInfo);
                            } else if (messageType.equals("notify")) {
                                if (notifyType.equals("property")) {
                                    deviceInfo.setProperty(data.getJsonObject("params").toString());
                                    deviceInfoRepository.save(deviceInfo);
                                } else if (notifyType.equals("event")) {
                                    DeviceLog deviceLog = new DeviceLog();
                                    deviceLog.setId(body.getString("id"));
                                    deviceLog.setDeviceName(deviceName);
                                    deviceLog.setProductId(productId);
                                    deviceLog.setType(notifyType);
                                    deviceLog.setData(data.toString());
                                    deviceLogRepository.save(deviceLog);
                                }
                            }
                            rc.response().end("");
                        } else {
                            try {
                                String msg = httpParams.get("msg").get(0);
                                checkSignature("111222aaabbb",
                                        msg,
                                        httpParams.get("nonce").get(0),
                                        httpParams.get("signature").get(0)
                                );
                                rc.response().end(msg);
                            } catch (Throwable e) {
                                e.printStackTrace();
                                rc.response().setStatusCode(500).end("error");
                            }
                        }
                    } catch (Throwable e) {
                        e.printStackTrace();
                        rc.response().setStatusCode(500).end(e.getMessage());
                    }
                });

        backendServer.requestHandler(backendRouter).listen(9083);
    }

    private static void checkSignature(String pushToken, String msg, String nonce, String signature) {
        if (StringUtils.isEmpty(msg) || StringUtils.isEmpty(nonce) || StringUtils.isEmpty(signature)) {
            throw new RuntimeException("no msg or nonce or signature field found in received data.");
        }
        String sig = makeSign(msg, pushToken, nonce);
        boolean rst = sig.equals(signature.replace(' ', '+'));
        System.out.printf("checkSignature: result %s,  detail receive:%s calculate:%s%n", rst, signature, sig);
        if (!rst) {
            throw new RuntimeException("check signature failed.");
        }
    }

    public static String makeSign(String msg, String token, String nonce) {
        byte[] signBytes = new byte[token.length() + 8 + msg.length()];
        System.arraycopy(token.getBytes(), 0, signBytes, 0, token.length());
        System.arraycopy(nonce.getBytes(), 0, signBytes, token.length(), 8);
        System.arraycopy(msg.getBytes(), 0, signBytes, token.length() + 8, msg.length());
        try {
            return Base64.encodeBase64String(MessageDigest.getInstance("MD5").digest(signBytes));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return "";
        }
    }

    private static Map<String, List<String>> getListData(MultiMap multiMap) {
        Map<String, List<String>> listData = new HashMap<>();
        for (Map.Entry<String, String> entry : multiMap.entries()) {
            String key = entry.getKey();
            String value = entry.getValue();
            listData.putIfAbsent(key, new ArrayList<>());
            listData.get(key).add(value);
        }
        return listData;
    }

    private static Map<String, Object> getData(MultiMap multiMap) {
        Map<String, Object> data = new HashMap<>();
        for (Map.Entry<String, String> entry : multiMap.entries()) {
            data.put(entry.getKey(), entry.getValue());
        }
        return data;
    }

    @Override
    public void run(String... args) throws Exception {
        startGateway();
    }
}
